﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.SceneGraph;
using Daybreak.Core.Rendering;
using Daybreak.Core.Rendering.Pipeline;
using Daybreak.Core.Input;

namespace Daybreak.Core
{
    public class Engine
    {
        public static Engine Instance;

        const float deltaConst = 1f / 60f;
        DateTime _lastUpdateTime;

        IGraphicsDeviceService _deviceService;

        UpdateVisitor _updater;
        CullVisitor _culler;
        //RenderVisitor _render;
        QueueRenderer _render;
        Rasterizer _rasterizer;
        Pipes.FrameContext _frameData;
        Scene _scene;

        public Camera Camera;
        public readonly KeyboardDevice Keyboard = new KeyboardDevice(60);
        public readonly MouseDevice Mouse = new MouseDevice(60);

        public static bool DoZSorting = false;

        public Engine(IGraphicsDeviceService gfxService)
        {
            _deviceService = gfxService;

            if (Instance != null) // only one engine instance allowed
                throw new InvalidOperationException("Multiple engine instances");

            Instance = this;

            _frameData = new Pipes.FrameContext();
            _updater = new UpdateVisitor(60);
            _culler = new CullVisitor(_frameData, 60);
            //_render = new RenderVisitor();
            _render = new QueueRenderer(_frameData, _culler);
            Camera = new Camera();
            Camera.Position = new Vector3(0, 6, -10);
            _rasterizer = new Rasterizer(_frameData, 0);
            
        }

        public void ShowScene(Scene scene)
        {
            _scene = scene;
        }

        private void Update(float elapsedTime)
        {
            // update all scene nodes (60 times per second)
            _updater.VisitScene(_scene.SceneInstance, Camera, elapsedTime);

            // find all visible scene nodes (20 times per second)
            _culler.VisitScene(_scene.SceneInstance, Camera, elapsedTime);
        }

        private void UpdateInput(float elapsedTime)
        {
            Keyboard.Update(elapsedTime);
            Mouse.Update(elapsedTime);
        }

        public float Tick()
        {
            float elapsedTime = 0;

            DateTime curTime = DateTime.Now;

            if (_lastUpdateTime.Year == 0)// first time
            {

                elapsedTime = deltaConst;

                _lastUpdateTime = curTime;

            }
            else
            {
                TimeSpan span = curTime - _lastUpdateTime;
                _lastUpdateTime = curTime;

                elapsedTime = (float)span.Milliseconds / 1000f;
            }

            if (_scene == null)
                return elapsedTime;

            // call all updaters
            UpdateInput(elapsedTime);
            Update(elapsedTime);

            // draw frame
            _render.RenderScene(_scene, Camera, elapsedTime);
            _rasterizer.DrawFrame(_render.RenderQueue, Device, Camera);

            return elapsedTime;
        }

        public static Texture2D LoadTexture(Scene.ImageLib lib, string file)
        {
            return Texture2D.FromFile(Instance.Device, file);
        }

        public static VertexDeclaration CreateDeclaration(VertexElement[] elements)
        {
            if (!IsReady)
                return null;

            return new VertexDeclaration(Instance.Device, elements);
        }

        public static GraphicsDevice GraphicsDevice
        {
            get
            {
                return Instance.Device;
            }
        }

        public GraphicsDevice Device
        {
            get { return _deviceService.GraphicsDevice; }
        }

        /// <summary>
        /// True if GraphicsDevice is initialized already
        /// </summary>
        public static bool IsReady
        {
            get
            {
                if (Instance == null)
                    return false;

                if (Instance._deviceService == null)
                    return false;

                object device = Instance.Device;

                if (device == null)
                    return false;

                return true;
            }
        }

        public int UpdateRate
        {
            get 
            {
                if (_updater.TimeStep.UpdateRate == 0)
                    return 0;

                return (int)(1.001f / _updater.TimeStep.UpdateRate); 
            }
            set 
            {
                if (value == 0)
                {
                    _updater.TimeStep.UpdateRate = 0;

                    return;
                }

                _updater.TimeStep.UpdateRate = 1f / value; 
            }
        }

        public int CullRate
        {
            get
            {
                if (_culler.UpdateRate == 0)
                    return 0;

                return (int)(1.001f / _culler.UpdateRate);
            }
            set
            {
                if (value == 0)
                {
                    _culler.UpdateRate = 0;

                    return;
                }

                _culler.UpdateRate = 1 / value;
            }
        }

    }

    public struct TimeSpanWatcher
    {
        public float UpdateRate;
        private float _delta;

        public TimeSpanWatcher(float spansPerSecond)
        {
            UpdateRate = 0;
            _delta = 0;

            if (spansPerSecond == 0)
                UpdateRate = 0;
            else
                UpdateRate = 1.0f / spansPerSecond;

        }

        /// <summary>
        /// Returns 0 if the next span is not ready, if it is ready - returns next time span (in seconds)
        /// </summary>
        /// <param name="elapsedTime">time in seconds sience last call</param>
        /// <returns></returns>
        public float GetNextDelta(float elapsedTime)
        {
            if (elapsedTime == 0)
                return 0;

            _delta += elapsedTime;

            float result = _delta;

            if (UpdateRate == 0)// update each frame
            {
                _delta = 0;

                return result;
            }
            else
                if (_delta >= UpdateRate)
                {
                    _delta = 0;

                    return result;
                }

            return 0;

        }

    }

    /// <summary>
    /// Helper struct to hold all necessory info about visible item
    /// </summary>
    public struct RenderArgs
    {
        public Matrix TransformMatrix;
        public Transform TransformNode;
        public BoundingBox BoundingBox;

    }

}
